package com.blustreaksw.cyclecoach.btle;

import java.util.List;
import java.util.UUID;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.NavUtils;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

import com.example.homescreendemosidemenu.R;

/* this activity's purpose is to show how to use particular type of devices in easy and fast way */
public class BtleDeviceInfoActivity extends Activity {
	
	public static final String EXTRAS_DEVICE_NAME    = "com.blustreaksw.cyclecoach.extras_device_name";
    public static final String EXTRAS_DEVICE_ADDRESS = "com.blustreaksw.cyclecoach.extras_device_address";
    public static final String EXTRAS_SERVICE_UUID = "com.blustreaksw.cyclecoach.extras_service_uuid";
    public static final String EXTRAS_USER_NAME = "com.blustreaksw.cyclecoach.extras_user_name";
	
	private Handler mHandler = null;
	private BluetoothManager mBTManager = null;
	private BluetoothAdapter mBTAdapter = null;
	private BluetoothDevice  mBTDevice = null;
	private BluetoothGatt    mBTGatt = null;
	private BluetoothGattService        mBTService = null;
	private BluetoothGattCharacteristic mBTValueCharacteristic = null;

	final static private UUID mHeartRateServiceUuid = BleDefinedUUIDs.Service.HEART_RATE;
	final static private UUID mCyclingPowerServiceUuid = BleDefinedUUIDs.Service.CYCLING_POWER;
	final static private UUID mCyclingSpeedCadenceServiceUuid = BleDefinedUUIDs.Service.CYCLING_SPEED_CADENCE;
	
	private String mDeviceAddress;
	private String mDeviceName;
	private String mUserName;
	
	private enum BtleServices {
		HEART_RATE(0), CYCLING_POWER(1), CYCLING_SPEED_CADENCE(2);
		private int value;
		
		private BtleServices(int v) {
			this.value = v;
		}
		
		@Override
		public String toString() {
			switch(this) {
			case HEART_RATE:
				return new String("Heart Rate");
			case CYCLING_POWER:
				return new String("Cycling Power");
			case CYCLING_SPEED_CADENCE:
				return new String("Cycling Speed and Cadence");
			}
			
			return null;
		}
		
		public UUID serviceUUID() {
			switch(this) {
			case HEART_RATE:
				return BleDefinedUUIDs.Service.HEART_RATE;
			case CYCLING_POWER:
				return BleDefinedUUIDs.Service.CYCLING_POWER;
			case CYCLING_SPEED_CADENCE:
				return BleDefinedUUIDs.Service.CYCLING_SPEED_CADENCE;
			}
			
			return null;
		}
		
		public UUID characteristicUUID() {
			switch(this) {
			case HEART_RATE:
				return BleDefinedUUIDs.Characteristic.HEART_RATE_MEASUREMENT;
			case CYCLING_POWER:
				return BleDefinedUUIDs.Characteristic.CYCLING_POWER_MEASUREMENT;
			case CYCLING_SPEED_CADENCE:
				return BleDefinedUUIDs.Characteristic.CYCLING_SPEED_CADENCE_MEASUREMENT;
			
			}
			
			return null;
		}
		
		public UUID batteryLevelCharacteristicUUID() {
			return BleDefinedUUIDs.Characteristic.BATTERY_LEVEL;
		}
		
		public int getValue() {
			return this.value;
		}
	}
	
	private BtleServices mTargetBtleService;
	private UUID mTargetServiceUuid;
	private boolean mDeviceNotificationActive;
	
	
	/*
	 * Layout: activity_btle_device_pairing.xml
	 */
	private TextView mBtleDevicePairing_deviceType;
	private TextView mBtleDevicePairing_address;
	private TextView mBtleDevicePairing_name;
	private TextView mBtleDevicePairing_testValues;
	private Button mBtleDevicePairing_connectButton;
	
	private void configureLayout() {
		mBtleDevicePairing_deviceType = (TextView)findViewById(R.id.btleDevicePairing_deviceType);
		mBtleDevicePairing_address = (TextView)findViewById(R.id.btleDevicePairing_address);
		mBtleDevicePairing_name = (TextView)findViewById(R.id.btleDevicePairing_name);
		mBtleDevicePairing_testValues = (TextView)findViewById(R.id.btleDevicePairing_testValues);
		mBtleDevicePairing_connectButton = (Button)findViewById(R.id.btleDevicePairing_connectButton);
	}
	
	private void connectLayoutElements() {
		mBtleDevicePairing_name.setText(mDeviceName);
		mBtleDevicePairing_address.setText(mDeviceAddress);
		
		mBtleDevicePairing_connectButton.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View arg0) {
				// TODO Auto-generated method stub
				
			}
			
		});
		
		if(mTargetServiceUuid.equals(mHeartRateServiceUuid)) {
			mBtleDevicePairing_deviceType.setText("Heart Rate Monitor");
		} else if(mTargetServiceUuid.equals(mCyclingPowerServiceUuid)) {
			mBtleDevicePairing_deviceType.setText("Cycling Power Meter");
		} else if(mTargetServiceUuid.equals(mCyclingSpeedCadenceServiceUuid)) {
			mBtleDevicePairing_deviceType.setText("Cycling Speed and Cadence Sensor");
		} else {
			mBtleDevicePairing_deviceType.setText("Unknown Type");
		}
		
	}
	
	/*
	 * SharedPreferences string definitions
	 */
	private static final String SP_BTLE_DEVICE_PAIRING = "com.blustreaksw.cyclecoach.sp_btle_device_pairing";
	private static final String SP_BTLE_DEVICE_TYPE_UUID = "com.blustreaksw.cyclecoach.sp_btle_device_type_uuid";
	private static final String SP_BTLE_DEVICE_ADDRESS = "com.blustreaksw.cyclecoach.sp_btle_device_address";
	private static final String SP_BTLE_DEVICE_NAME = "com.blustreaksw.cyclecoach.sp_btle_device_name";
	private static final String SP_BTLE_USER_NAME = "com.blustreaksw.cyclecoach.sp_btle_user_name";
	private static final String SP_BTLE_USER_PRESSED_CONNECT = "com.blustreaksw.cyclecoach.sp_btle_user_pressed_connect";
	
	
	/*
	 * Android GUI controls
	 */
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_btle_device_pairing);
		log("Creating activity");
		configureLayout();
		
		// Show the Up button in the action bar.
		getActionBar().setDisplayHomeAsUpEnabled(true);

		mHandler = new Handler();
		log("Activity created");
		
		final Intent intent = getIntent();
		if((mDeviceName = intent.getStringExtra(EXTRAS_DEVICE_NAME)) != null) {
			mDeviceAddress = intent.getStringExtra(EXTRAS_DEVICE_ADDRESS);
	        mTargetServiceUuid = UUID.fromString(intent.getStringExtra(EXTRAS_SERVICE_UUID));
	        mUserName = intent.getStringExtra(EXTRAS_USER_NAME);
	        saveValuesToSharedPreferences();
		}
        
        setTitle("BTLE Pairing");
		
	}
	
	private void saveValuesToSharedPreferences() {
		SharedPreferences sp = getSharedPreferences(SP_BTLE_DEVICE_PAIRING, MODE_PRIVATE);
        SharedPreferences.Editor editor = sp.edit();
		editor.putString(SP_BTLE_DEVICE_NAME, mDeviceName);
		editor.putString(SP_BTLE_DEVICE_ADDRESS, mDeviceAddress);
		editor.putString(SP_BTLE_DEVICE_TYPE_UUID, mTargetServiceUuid.toString());
		editor.putString(SP_BTLE_USER_NAME, mUserName);
		editor.putBoolean(SP_BTLE_USER_PRESSED_CONNECT, false);
		editor.commit();
	}
	
	private void restoreValuesFromSharedPreferences() {
		SharedPreferences sp = getSharedPreferences(SP_BTLE_DEVICE_PAIRING, MODE_PRIVATE);
		mDeviceAddress = sp.getString(SP_BTLE_DEVICE_ADDRESS, null);
		mDeviceName = sp.getString(SP_BTLE_DEVICE_NAME, null);
		mUserName = sp.getString(SP_BTLE_USER_NAME, null);
		mTargetServiceUuid = UUID.fromString(sp.getString(SP_BTLE_DEVICE_TYPE_UUID, null));
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		switch (item.getItemId()) {
		case android.R.id.home:
			NavUtils.navigateUpFromSameTask(this);
			return true;
		}
		return super.onOptionsItemSelected(item);
	}

	@Override
	protected void onResume() {
		super.onResume();
		log("Resuming activity");
		
		// first check if BT/BLE is available and enabled
		if(initBt() == false) return;
		if(isBleAvailable() == false) return;
		if(isBtEnabled() == false) return;
		
		if (!isBtEnabled()) {
			// Return to ScanningActivity, which will request that BT be enabled.
			startActivity(new Intent(this, ScanningActivity.class)); 
        }
		
		if(mDeviceAddress == null || mDeviceAddress.equals("")) {
			restoreValuesFromSharedPreferences();
		}
		
		connectLayoutElements();
		
		mDeviceNotificationActive = false;
		
		if(mDeviceAddress != null) {
			// then start discovering devices around
			log("Searching for sensor " + mDeviceAddress);
			startSearchingForDevice();
		}
		else {
			log("No sensor address was passed by the calling Activity.");
			NavUtils.navigateUpFromSameTask(this);
		}
		
		log("Activity resumed");
	}
	
	@Override
	protected void onPause() {
		super.onPause();
		
		if(mDeviceNotificationActive) {
			disableNotificationForDevice();
		}
		disconnectFromDevice();
		closeGatt();
	};

	private boolean initBt() {
		mBTManager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);
		if(mBTManager != null) mBTAdapter = mBTManager.getAdapter();
		
		return (mBTManager != null) && (mBTAdapter != null);
	}
	
	private boolean isBleAvailable() {
		log("Checking if BLE hardware is available");
		
		boolean hasBle = getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
		if(hasBle && mBTManager != null && mBTAdapter != null) {
			log("BLE hardware available");
		}
		else {
			log("BLE hardware is missing!");
			return false;
		}
		return true;
	}
	
	private boolean isBtEnabled() {
		log("Checking if BT is enabled");
		if(mBTAdapter.isEnabled()) {
			log("BT is enabled");
		}
		else {
			log("BT is disabled. Use Setting to enable it and then come back to this app");
			return false;
		}
		return true;
	}

	private void startSearchingForDevice() {
		// we define what kind of services found device needs to provide.
		final UUID[] uuids = new UUID[] { mTargetServiceUuid };//, 
		mBTAdapter.startLeScan(uuids, mDeviceFoundCallback);
		// results will be returned by callback
		log("Started searching for devices");
		
		// please, remember to add timeout for that scan
		Runnable timeout = new Runnable() {
            @Override
            public void run() {
				if(mBTAdapter.isDiscovering() == false) return;
				stopSearchingForDevices();	
            }
        };
        mHandler.postDelayed(timeout, 10000); //10 seconds		
	}

	private void stopSearchingForDevices() {
		mBTAdapter.stopLeScan(mDeviceFoundCallback);
		log("Stopped searching for device services.");
	}
	
	private void connectToDevice() {
		log("Connecting to the device NAME: " + mBTDevice.getName() + " HWADDR: " + mBTDevice.getAddress());
		mBTGatt = mBTDevice.connectGatt(this, true, mGattCallback);
	}
	
	private void disconnectFromDevice() {
		log("Disconnecting from device");
		if(mBTGatt != null) mBTGatt.disconnect();
	}
	
	private void closeGatt() {
		if(mBTGatt != null) mBTGatt.close();
		mBTGatt = null;
	}
	
	private void discoverServices() {
		log("Starting discovering services");
		mBTGatt.discoverServices();
	}
	
	private void analyzeServices() {
		log("Checking services available");
		
		List<BluetoothGattService> services = mBTGatt.getServices();
		
		for(BluetoothGattService s: services) {
			UUID uuid= s.getUuid();
			if(uuid.equals(BtleServices.HEART_RATE.serviceUUID())) {
				log("Found heart rate service.");
				mTargetBtleService = BtleServices.HEART_RATE;
				getService();
				break;
			} else if(uuid.equals(BtleServices.CYCLING_POWER.serviceUUID())) {
				log("Found cycling power service");
				mTargetBtleService = BtleServices.CYCLING_POWER;
				getService();
				break;
			} else if(uuid.equals(BtleServices.CYCLING_SPEED_CADENCE.serviceUUID())) {
				log("Found cycling speed and cadence service");
				mTargetBtleService = BtleServices.CYCLING_SPEED_CADENCE;
				getService();
				break;
			} else {
				log("Device does not have a valid service that we support.");
			}
		}
	}
	
	private void getService() {
		log(String.format("Getting %s service", mTargetBtleService.toString()));
		mBTService = mBTGatt.getService(mTargetBtleService.serviceUUID());
		
		if(mBTService == null) {
			log(String.format("Could not get %s service", mTargetBtleService.toString()));
		}
		else {
			log(String.format("%s service successfully retrieved.", mTargetBtleService.toString()));
			getCharacteristic();
		}
	}
	
	private void getCharacteristic() {
		log(String.format("Getting %s Measurement characteristic", mTargetBtleService.toString()));
		mBTValueCharacteristic = mBTService.getCharacteristic(mTargetBtleService.characteristicUUID());
		
		if(mBTValueCharacteristic == null) {
			log(String.format("Could not find %s Measurement characteristic", mTargetBtleService.toString()));
		}
		else {
			log(String.format(" %s Measurement characteristic retrieved.", mTargetBtleService.toString()));
			enableNotificationForDevice();
		}
	}
	
	private void enableNotificationForDevice() {
		log(String.format("Enabling notification for %s.", mDeviceName));
        boolean success = mBTGatt.setCharacteristicNotification(mBTValueCharacteristic, true);
        if(!success) {
        	log("Enabling notification failed!");
        	return;
        }
        
        mDeviceNotificationActive = true;

        BluetoothGattDescriptor descriptor = mBTValueCharacteristic.getDescriptor(BleDefinedUUIDs.Descriptor.CHAR_CLIENT_CONFIG);
        if(descriptor != null) {
	        descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
	        mBTGatt.writeDescriptor(descriptor);
	        log("Notification enabled");
        }		
        else {
        	log("Could not get descriptor for characteristic! Notification are not enabled.");
        }
	}

	private void disableNotificationForDevice() {
		if(mBTGatt == null) {
			return;
		}
		
		log("Disabling notification for " + mDeviceName);

        boolean success = mBTGatt.setCharacteristicNotification(mBTValueCharacteristic, false);
        if(!success) {
        	log("Disabling notification failed!");
        	return;
        }
		
		mDeviceNotificationActive = false;

        BluetoothGattDescriptor descriptor = mBTValueCharacteristic.getDescriptor(BleDefinedUUIDs.Descriptor.CHAR_CLIENT_CONFIG);
        if(descriptor != null) {
	        descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
	        mBTGatt.writeDescriptor(descriptor);
	        log("Notification disabled");
        }		
        else {
        	log("Could not get descriptor for characteristic! Notification could be still enabled.");
        }
	}	
	
	private void getAndDisplayValue() {
    	switch(mTargetBtleService) {
    	case HEART_RATE:
    		getAndDisplayHeartRate();
    		break;
		case CYCLING_POWER:
			getAndDisplayPowerMeasurement();
			break;
		case CYCLING_SPEED_CADENCE:
			getAndDisplayCyclingSpeedAndCadenceMeasurement();
			break;
		default:
			break;
    	}
	}
	
	private void getAndDisplayHeartRate() {
		byte[] raw = mBTValueCharacteristic.getValue();
    	int index = ((raw[0] & 0x01) == 1) ? 2 : 1;
    	int format = (index == 1) ? BluetoothGattCharacteristic.FORMAT_UINT8 : BluetoothGattCharacteristic.FORMAT_UINT16;
    	int value = mBTValueCharacteristic.getIntValue(format, index);
    	final String description = value + " bpm";

    	runOnUiThread(new Runnable() {
			@Override
			public void run() {
				mBtleDevicePairing_testValues.setText(description);
			}
    	});
	}
	
	private void getAndDisplayPowerMeasurement() {
		final int instantPowerWatts = mBTValueCharacteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_SINT16, 2);
		final String description = instantPowerWatts + " watts";
		
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				mBtleDevicePairing_testValues.setText(description);
			}
    	});	
	}
	
	private void getAndDisplayCyclingSpeedAndCadenceMeasurement() {
		final int totalWheelRevs = mBTValueCharacteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 1);
		final int totalCrankRevs = mBTValueCharacteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 7);
		final String description = totalWheelRevs + " wheel revs\n" + totalCrankRevs + " crank revs";
		
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				mBtleDevicePairing_testValues.setText(description);
			}
    	});
	}
	
    private BluetoothAdapter.LeScanCallback mDeviceFoundCallback = new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) {
        	// here we found some device with heart rate service, lets save it:
        	
        	log("Device discovered. HW Address: "  + device.getAddress());
        	
        	if(device.getAddress().equals(mDeviceAddress)) {
        		BtleDeviceInfoActivity.this.mBTDevice = device;
        		stopSearchingForDevices();
        		connectToDevice();
        	}
        	else {
        		log("Device " + device.getAddress() + " is not our device, continuing search...");
        	}
        }
    };	
	
    /* callbacks called for any action on HR Device */
    private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            if (newState == BluetoothProfile.STATE_CONNECTED) {
            	log("Device connected");
            	discoverServices();
            }
            else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
            	log("Device disconnected");
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        	if(status == BluetoothGatt.GATT_SUCCESS) {
        		log("Services discovered");
        		analyzeServices();
        	}
        	else {
        		log("Unable to discover services");
        	}
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt,
                                            BluetoothGattCharacteristic characteristic)
        {
        	if(characteristic.equals(mBTValueCharacteristic)) {
        		getAndDisplayValue();
        	}
        }       
        
        /* the rest of callbacks are not interested for us */
        
        @Override
        public void onCharacteristicRead(BluetoothGatt gatt,
                                         BluetoothGattCharacteristic characteristic,
                                         int status) {}


        
        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {};
        
        @Override
        public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {};
    };
     
	
	// put new logs into the UI console
	private void log(final String txt) {
		Log.d("BtleDeviceInfoActivity", txt);
	}
}
